package com.benjaminwan.read.free.ui.main

import android.bluetooth.BluetoothDevice
import android.content.*
import android.os.IBinder
import android.util.Range
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.benjaminwan.read.free.base.viewmodel.MvRxViewModel
import com.benjaminwan.read.free.base.viewmodel.state
import com.benjaminwan.read.free.common.ACTION_START_SCRIPT
import com.benjaminwan.read.free.common.ACTION_STOP_SCRIPT
import com.benjaminwan.read.free.common.SP_ACTION_SCRIPT
import com.benjaminwan.read.free.common.appPrefs
import com.benjaminwan.read.free.model.action.*
import com.benjaminwan.read.free.model.enums.Horizontal
import com.benjaminwan.read.free.model.enums.Vertical
import com.benjaminwan.read.free.service.HidWrapperService
import com.benjaminwan.read.free.utils.*
import com.benjaminwan.read.free.utils.HIDUtils.mouseDrag
import com.benjaminwan.read.free.utils.HIDUtils.sleep
import com.orhanobut.logger.Logger
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers


class MainViewModel(
    initialState: MainState,
    val context: Context
) : MvRxViewModel<MainState>(initialState) {

    private var spScript: Script by context.appPrefs.moshiActionAny(
        SP_ACTION_SCRIPT, Script()
    )
    var hidWrapper: HidWrapperService? = null

    private val mConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            Logger.i("onServiceConnected")
            hidWrapper = (service as HidWrapperService.LocalBinder).service
            hidWrapper?.setEventListener(hidEventListener)
        }

        override fun onServiceDisconnected(className: ComponentName) {
            hidWrapper = null
        }
    }

    private val hidEventListener = object : HidWrapperService.HidEventListener {
        override fun onApplicationState(registered: Boolean) {
            Logger.i("onApplicationState:$registered")
            setState { copy(appReady = registered) }
        }

        override fun onPluggedDeviceChanged(device: BluetoothDevice?) {
            Logger.i("onPluggedDeviceChanged:$device")
            setState { copy(plugDev = device) }
        }

        override fun onConnectionState(device: BluetoothDevice?, connected: Boolean) {
            Logger.i("onConnectionState:$device , connected=$connected")
            setState { copy(connected = connected) }
        }

        override fun onProtocolModeState(bootMode: Boolean) {
            Logger.i("onProtocolModeState:$bootMode")
            setState { copy(bootMode = bootMode) }
        }

        override fun onKeyboardLedState(numLock: Boolean, capsLock: Boolean, scrollLock: Boolean) {
            Logger.i("onKeyboardLedState:$numLock, $capsLock , $scrollLock")
            setState {
                copy(numLock = numLock, capsLock = capsLock, scrollLock = scrollLock)
            }
        }

        override fun onRunningState(isRunning: Boolean) {
            Logger.i("onRunningState:$isRunning")
            setScriptRunning(isRunning)
        }

        override fun onRunningPos(pos: Position) {
            setState {
                copy(currentPos = pos)
            }
        }

        override fun onGroupProgress(progress: LoopProgress) {
            setState {
                copy(groupProgress = progress)
            }
        }

        override fun onScriptProgress(progress: LoopProgress) {
            setState {
                copy(scriptProgress = progress)
            }
        }
    }

    private val notificationReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent ?: return
            when (intent.action) {
                ACTION_START_SCRIPT -> {
                    Logger.i(ACTION_START_SCRIPT)
                    startScript()
                }
                ACTION_STOP_SCRIPT -> {
                    Logger.i(ACTION_STOP_SCRIPT)
                    stopScript()
                }
                else -> {
                }
            }

        }

    }

    init {
        val intent = Intent(context, HidWrapperService::class.java)
        context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        setState {
            copy(script = spScript)
        }
        val filter = IntentFilter()
        filter.addAction(ACTION_START_SCRIPT)
        filter.addAction(ACTION_STOP_SCRIPT)
        context.registerReceiver(notificationReceiver, filter)
    }

    fun connect() {
        hidWrapper?.connect()
    }

    fun disConnect() {
        hidWrapper?.disconnect()
    }

    fun unplug() {
        hidWrapper?.unplug()
    }

    fun keyboardKeyDown(key: Int) {
        hidWrapper?.keyboardKeyDown(key.toByte())
    }

    fun keyboardKeyUp(key: Int) {
        hidWrapper?.keyboardKeyUp(key.toByte())
    }

    fun mouseMove(dx: Int, dy: Int) {
        hidWrapper?.mouseMove(dx, dy)
    }

    fun mouseScroll(delta: Int) {
        hidWrapper?.mouseScroll(delta.toByte())
    }

    fun mouseButtonDown(which: Int) {
        hidWrapper?.mouseButtonDown(which)
    }

    fun mouseButtonUp(which: Int) {
        hidWrapper?.mouseButtonUp(which)
    }

    fun setMoveSpeed(speed: Int) {
        setState {
            copy(moveSpeed = speed)
        }
    }

    fun setScrollSpeed(speed: Int) {
        setState {
            copy(scrollSpeed = speed)
        }
    }

    fun mouseButtonClick(which: Int) {
        Single.fromCallable {
            mouseButtonDown(which)
            sleep(1)
            mouseButtonUp(which)
        }.subscribeOn(Schedulers.computation())
            .observeOn(Schedulers.computation())
            .subscribe()
            .disposeOnClear()
    }

    fun mouseButtonDoubleClick(which: Int) {
        Single.fromCallable {
            mouseButtonDown(which)
            sleep(1)
            mouseButtonUp(which)
            sleep(2)
            mouseButtonDown(which)
            sleep(1)
            mouseButtonUp(which)
        }.subscribeOn(Schedulers.computation())
            .observeOn(Schedulers.computation())
            .subscribe()
            .disposeOnClear()
    }

    fun doMouseDrag(
        xDirection: Horizontal, yDirection: Vertical,
        dxRange: IntRange, dyRange: IntRange,
        stepXRange: IntRange, stepYRange: IntRange
    ) {
        Single.fromCallable {
            mouseDrag(hidWrapper, xDirection, yDirection, dxRange, dyRange, stepXRange, stepYRange)
        }.subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .execute { copy(dragRequest = it) }
    }

    fun gamePadButtonDown(which: Int) {
        hidWrapper?.gamePadButtonDown(which)
    }

    fun gamePadButtonUp(which: Int) {
        hidWrapper?.gamePadButtonUp(which)
    }

    fun gamePadAxis(dx: Int, dy: Int) {
        hidWrapper?.gamePadAxis(dx, dy)
    }

    fun gamePadAngleStrength(angle: Int, strength: Int) {
        Single.fromCallable {
            val rad = Math.toRadians(angle.toDouble())
            val dist = strength / 100.0

            val dx = dist * Math.cos(rad) * 127
            val dy = dist * Math.sin(rad) * -127

            val bounds = Range(-127, 127)
            val adjX = bounds.clamp(dx.toInt())
            val adjY = bounds.clamp(dy.toInt())
            Logger.i("angle=$angle strength=$strength rad=$rad dist=$dist dx=$dx dy=$dy adjX=$adjX adjY=$adjY")
            gamePadAxis(adjX, adjY)
        }.subscribeOn(Schedulers.computation())
            .observeOn(Schedulers.computation())
            .subscribe()
            .disposeOnClear()
    }

    fun setScriptRunning(isRunning: Boolean) {
        setState {
            copy(isScriptRunning = isRunning)
        }
    }

    fun setScript(script: Script) {
        setState {
            copy(script = script)
        }
    }

    fun startScript() {
        val script = this.state.script
        hidWrapper?.startScript(script)
    }

    fun stopScript() {
        hidWrapper?.stopScript()
    }

    fun setGroup(idGroup: Int, group: Group?) {
        setState {
            val groups = if (group == null) {
                this.script.groups.copyRemoveAt(idGroup)
            } else {
                this.script.groups.copyAndSet(idGroup, group)
            }
            copy(script = this.script.copy(groups = groups))
        }
    }

    fun addGroup() {
        setState {
            val groupCount = this.script.groups.size + 1
            val group = Group(memo = "组${groupCount}")
            val groups = this.script.groups.copyAdd(group)
            copy(script = this.script.copy(groups = groups))
        }
    }

    fun addGroup(index: Int, group: Group) {
        setState {
            val groups = this.script.groups.copyAdd(index, group)
            copy(script = this.script.copy(groups = groups))
        }
    }

    fun addActionToGroup(idGroup: Int, action: Action) {
        setState {
            val actions = this.script.groups[idGroup].actions.copyAdd(action)
            val group = this.script.groups[idGroup].copy(actions = actions)
            val groups = this.script.groups.copyAndSet(idGroup, group)
            copy(script = this.script.copy(groups = groups))
        }
    }

    fun setAction(pos: Position, action: Action?) {
        setState {
            val actions = if (action != null) {
                this.script.groups[pos.idGroup].actions.copyAndSet(pos.idAction, action)
            } else {
                this.script.groups[pos.idGroup].actions.copyRemoveAt(pos.idAction)
            }
            val group = this.script.groups[pos.idGroup].copy(actions = actions)
            val groups = this.script.groups.copyAndSet(pos.idGroup, group)
            copy(script = this.script.copy(groups = groups))
        }
    }

    fun saveSP() {
        val state = this.state
        spScript = state.script
    }

    fun toClipBoard() {
        val script = this.state.script
        val json = toActionJsonByMoshi(script)
        context.toClipboard(json)
    }

    fun fromClipBoardReplace() {
        //获取剪贴板管理器：
        val clipboardManager =
            context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        if (clipboardManager.hasPrimaryClip()) {
            val clipData = clipboardManager.primaryClip
            if (clipData != null) {
                if (clipData.itemCount > 0) {
                    val json = clipData.getItemAt(0).text.toString()
                    val script = fromJsonToActionByMoshi<Script>(json)
                    if (script != null) {
                        setScript(script)
                        context.showXToastSuccess("剪切板数据解析成功!")
                    } else {
                        context.showXToastError("剪切板数据解析失败!")
                    }
                } else {
                    context.showXToastError("剪切板找不到正确的数据!")
                }
            } else {
                context.showXToastError("剪切板获取失败!")
            }
        } else {
            context.showXToastError("剪切板没有数据!")
        }
    }

    fun fromClipBoardAdd() {
        val json = context.fromClipboard()
        if (json.isNullOrEmpty()) {
            context.showXToastError("剪切板找不到正确的数据!")
            return
        }
        val script = fromJsonToActionByMoshi<Script>(json)
        if (script != null) {
            setState {
                val groups = this.script.groups.copyAddAll(script.groups)
                copy(script = this.script.copy(groups = groups))
            }
            context.showXToastSuccess("剪切板数据添加成功!")
        } else {
            context.showXToastError("剪切板数据解析失败!")
        }
    }

    override fun onCleared() {
        if (hidWrapper != null) {
            context.unbindService(mConnection)
        }
        context.unregisterReceiver(notificationReceiver)
        super.onCleared()
    }

    companion object : MvRxViewModelFactory<MainViewModel, MainState> {
        override fun create(
            viewModelContext: ViewModelContext,
            state: MainState
        ): MainViewModel? {
            val context = viewModelContext.activity.applicationContext
            return MainViewModel(state, context)
        }
    }
}